package com.xiaomaoguai.webkit.io;

import static org.springframework.util.ResourceUtils.FILE_URL_PREFIX;

import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.Constants;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import com.xiaomaoguai.webkit.core.utils.XmgFileUtils;

/**
 * 提供了获取资源的统一入口，支持正则表达式定位一个或多个资源，如：<code>classpath*:META-INF/nio-*.xml</code>
 * 将查找所有jar中META-INF目录事以nio-开头的xml文件。 资源主要有：
 * <p>
 * 应用系统资源：如<code>{@link #FOLDER_ETC}</code>等目录下的文件
 * <p>
 * classpath资源：以<code>{@link #PREFIX_CLASSPATH_URL}</code>为前缀，可能有多个重名资源，只加载一个
 * <p>
 * classpath资源集：以<code>{@link #PREFIX_CLASSPATH_URL_ALL}为前缀，可能有多个重名资源，全部加载
 *  <p>文件系统：本地文件系统资源，如<code>PREFIX_FILE_URL</code>，定位本地系统资源，如
 * <code>C:\program files\test.txt</code>
 * 
 * @author lindeshu <deshu.lin@audaque.com>
 */
public class ResourceUtils {


    /** classpath资源（可能有多个重名资源） */
    public static final String PREFIX_CLASSPATH_URL_ALL = "classpath*:";
    /** classpath资源（可能有多个重名资源，只加载一个） */
    public static final String PREFIX_CLASSPATH_URL = "classpath:";
    /** 本地系统资源 */
    public static final String PREFIX_FILE_URL = "file:";
    /** 系统资源目录：etc*/
    public final static String FOLDER_ETC = "etc";
    /** 系统资源目录：bin*/
    public final static String FOLDER_BIN = "bin";
    /** 系统资源目录：data*/
    public static final String FOLDER_DATA = "data";
    /** 系统资源目录：help*/
    public final static String FOLDER_HELP = "help";
    /** 系统资源目录：shared*/
    public final static String FOLDER_SHARED = "shared";

    /**
     * 扩展的资源匹配解析器
     */
    private static class AudaqueResourcePatternResolver extends PathMatchingResourcePatternResolver {
        public AudaqueResourcePatternResolver() {
        }

        @Override
        public Resource[] getResources(String locationPattern) throws IOException {
            return super.getResources(resolveSystemLocationPattern(locationPattern));
        }

    }
    private final static ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new AudaqueResourcePatternResolver();
    
    /**
     * 解析资源定位信息
     * @param locationPattern   资源定位信息
     * @return  解析后的资源定位信息
     */
    private static String resolveSystemLocationPattern(String locationPattern) {
        for (String folder : folders) {
            String prefix = folder;
            if (locationPattern.startsWith(prefix)) {
                prefix = folder + ":";
                if (!StringUtils.startsWithIgnoreCase(locationPattern, prefix)) {
                    continue;
                }
                String fileName = locationPattern.substring(prefix.length());
                String resolvedLocationPattern = FILE_URL_PREFIX + XmgFileUtils.getSystemFileCanonicalPath(folder, fileName);
                return resolvedLocationPattern;
            }
        }
        
        return locationPattern;
    }

    /**
     * Resolve the given location pattern into Resource objects.
     * <p>Overlapping resource entries that point to the same physical
     * resource should be avoided, as far as possible. The result should
     * have set semantics.
     * @param locationPattern the location pattern to resolve. e.g file:c:\xx.txt, classpath:com/\\*\\*\/\\*.properties etc:scheduler.properties
     * @return the corresponding Resource objects
     * @throws IOException in case of I/O errors
     */
    public static Resource[] getResources(String locationPattern) throws IOException {
        return RESOURCE_PATTERN_RESOLVER.getResources(locationPattern);
    }
    
    /**
     * 获取资源集
     * @param locationPattherns 资源定位路径
     * @return  资源集
     * @throws IOException  in case of IO error
     */
    public static Resource[] getResources(String[] locationPattherns) throws IOException {
        if(locationPattherns == null) {
            return new Resource[0];
        }
        Set<Resource> result = new LinkedHashSet<Resource>();
        for (String locationPattern : locationPattherns) {
            if (StringUtils.isEmpty(locationPattern))   continue;
            for (Resource resource : ResourceUtils.getResources(locationPattern)) {
                if (resource == null)   continue;
                result.add(resource);
            }
        }
        return result.toArray(new Resource[result.size()]);
    }
    
     /**
     * 获取资源集
     * @param locationPattherns 资源定位路径
     * @return  返回带编码的字符集
     * @throws IOException  in case of IO error
     */
    public static EncodedResource[] getResources(String[] locationPattherns, String charset) throws IOException {
        if(locationPattherns == null) {
            return new EncodedResource[0];
        }
        Set<EncodedResource> result = new LinkedHashSet<EncodedResource>();
        for (String locationPattern : locationPattherns) {
            if (StringUtils.isEmpty(locationPattern))   continue;
            for (Resource resource : ResourceUtils.getResources(locationPattern)) {
                if (resource == null)   
		    continue;

                result.add(new EncodedResource(resource, charset));
            }
        }
	
        return result.toArray(new EncodedResource[result.size()]);
    }
    
    public static String convertToFilepathPattern(String locationPattern) {
        if(StringUtils.isEmpty(locationPattern)) {
            return locationPattern;
        }
        return PREFIX_FILE_URL + StringUtils.removeStartIgnoreCase(locationPattern, PREFIX_FILE_URL);
    }
    
    /**
     * 将资源路径转换classpath的路径
     * @param locationPattern   原路径
     */
    public static String convertToClasspathPattern(String locationPattern) {
        if(StringUtils.isEmpty(locationPattern)) {
            return locationPattern;
        }
        if(StringUtils.startsWithIgnoreCase(locationPattern, PREFIX_CLASSPATH_URL)) {
            locationPattern = locationPattern.substring(PREFIX_CLASSPATH_URL.length());
        }
        return PREFIX_CLASSPATH_URL + locationPattern;
    }
    
    /**
     * 将资源路径转换系统资源路径
     * @param systemFolder 资源目录
     * @param locationPattern   原路径
     */
    public static String convertToSystemPattern(String systemFolder, String locationPattern) {
        if(StringUtils.isEmpty(locationPattern)) {
            return locationPattern;
        }
        for (String folder : folders) {
            String prefix = folder;
            if (locationPattern.startsWith(prefix)) {
                prefix = folder + ":";
                if (!StringUtils.startsWithIgnoreCase(locationPattern, prefix)) {
                    continue;
                }
                String fileName = locationPattern.substring(prefix.length());
                String resolvedLocationPattern = FILE_URL_PREFIX + XmgFileUtils.getSystemFileCanonicalPath(systemFolder, fileName);
                return resolvedLocationPattern;
            }
        }
        return FILE_URL_PREFIX + XmgFileUtils.getSystemFileCanonicalPath(systemFolder, locationPattern);
    }
    
    /**
     * 将资源路径转换系统资源路径
     * @param locationPattern   原路径
     */
    public static String convertToAllClasspathPattern(String locationPattern) {
        if(StringUtils.isEmpty(locationPattern)) {
            return locationPattern;
        }
        if(StringUtils.startsWithIgnoreCase(locationPattern, PREFIX_CLASSPATH_URL_ALL)) {
            locationPattern = locationPattern.substring(PREFIX_CLASSPATH_URL_ALL.length());
        }
        return PREFIX_CLASSPATH_URL_ALL + locationPattern;
    }

    /**
     * Resolve the given location pattern into Resource objects.
     * <p>Overlapping resource entries that point to the same physical
     * resource should be avoided, as far as possible. The result should
     * have set semantics.
     * @param locationPattern the location pattern to resolve. e.g file:c:\xx.txt, classpath:com/\\*\\*\/\\*.properties etc:scheduler.properties
     * @return the Resource object(return the first resource if there are more than one resources matched)
     * @throws IOException in case of I/O errors
     */
    public static Resource getResource(String location) throws IOException {
        Resource[] resources = getResources(location);
        return ArrayUtils.isNotEmpty(resources) ? resources[0] : null;
    }
    
    /**
     * 获取系统资源文件
     * @param folder    程序目录，如{@link #FOLDER_BIN}
     * @param fileName     文件名称
     * @return      资源信息
     * @throws IOException  in case of I/O errors
     */
    public static Resource getSystemResource(String folder, String fileName) throws IOException {
        Resource[] resources = getResources(folder + ":" + fileName);
        return ArrayUtils.isNotEmpty(resources) ? resources[0] : null;
    }

    /**
     * 获取所有classpath下的资源（如果有多个jar中包含有同名文件，则将全部加载）
     * @param locationPatthern  classpath资源表达式
     * @return  匹配的所有资源信息
     * @throws IOException  in case of I/O errors
     */
    public static Resource[] getAllClasspathResources(String locationPatthern) throws IOException {
        return getResources(PREFIX_CLASSPATH_URL_ALL + locationPatthern);
    }
    
    /**
     * 合并资源集
     * @param resources 要合并的资源集
     * @return  合并后的资源集
     * @throws IOException  in case of IO error
     */
    public static Resource[] merge(Resource[]... resources) {
        if (resources == null) {
            return new Resource[0];
        }
        Set<Resource> result = new LinkedHashSet<Resource>();
        for (Resource[] collection : resources) {
            if (collection == null) continue;
            for (Resource resource : collection) {
                if (resource == null)   continue;
                result.add(resource);
            }
        }
        return result.toArray(new Resource[result.size()]);
    }
    
    /**
     * 获取classpath的资源
     * @param locationPattern   资源定位信息，支持通配符
     * @return  Resource集合
     * @throws IOException  in case of I/O errors
     */
    public static Resource[] getClasspathResources(String locationPattern) throws IOException {
        return getResources(PREFIX_CLASSPATH_URL + locationPattern);
    }

    /**
     * 获取classpath的资源
     * @param locationPattern   资源定位信息
     * @return  Resource，如果有多个资源符合条件，则只返回第一个
     * @throws IOException  in case of I/O errors
     */
    public static Resource getClasspathResource(String locationPattern) throws IOException {
        Resource[] resources = getResources(PREFIX_CLASSPATH_URL + locationPattern);
        return ArrayUtils.isNotEmpty(resources) ? resources[0] : null;
    }

    /**
     * 获取文件系统的资源
     * @param locationPattern   资源定位信息，支持通配符
     * @return  Resource集合
     * @throws IOException  in case of I/O errors
     */
    public static Resource[] getFileResources(String locationPattern) throws IOException {
        return getResources(FILE_URL_PREFIX + locationPattern);
    }

    /**
     * 获取文件系统的资源
     * @param locationPattern   资源定位信息
     * @return  Resource，如果有多个资源符合条件，则只返回第一个
     * @throws IOException  in case of I/O errors
     */
    public static Resource getFileResource(String locationPattern) throws IOException {
        Resource[] resources = getResources(FILE_URL_PREFIX + locationPattern);
        return ArrayUtils.isNotEmpty(resources) ? resources[0] : null;
    }
    
    //程序根目录下的自定义目录名集合
    private final static Set<String> folders = new HashSet<String>();

    /**
     * 初始化已定义的目录名
     */
    private static void initFolders() {
        Constants CONSTANTS = new Constants(ResourceUtils.class);
        Set<String> folderConstantsKeys = CONSTANTS.getNames("FOLDER_");

        for (String key : folderConstantsKeys) {
            String folder = CONSTANTS.asString(key);
            if (StringUtils.isNotEmpty(folder)) {
                folders.add(folder);
            }
        }
    }

    static {
        initFolders();
    }

    private ResourceUtils() {
    }
}
